Variables
- ID: identificación de vuelo.
- DATOP: Fecha de vuelo.
- FLTID: Número de vuelo
- DEPSTN: Punto de partida
- ARRSTN: Punto de llegada
- STD: Hora de salida programada
- STA: Hora prevista de llegada
- STATUS: Estado del vuelo
- AC: Código de aeronave
- target: Tiempo de retraso
Train y Test
- Train:
- En el conjunto de datos de train hay 107833 vuelos registrados.
- El rango de fechas de vuelos es desde el 01-01-2016 hasta 31-12-2018.
- Hay 1861 vuelos registrados. Cada vuelo puede estar registrado en diferentes fechas.
- En total son 132 puntos de destino diferentes y 128 puntos de llegada.
- Hay en total 5 estados (STATUS) de vuelo.
- Hay 68 códigos de aeronaves diferentes.
- El tiempo de retraso (target) está dado en minutos.
library(tidyverse)
# Train
dataTrain %>%
mutate(DATOP = as.Date(DATOP),
FLTID = as.factor(FLTID),
DEPSTN = as.factor(DEPSTN),
ARRSTN = as.factor(ARRSTN),
STD = as.POSIXct(STD),
STA = gsub("\\.", ":", STA),
STA = as.POSIXct(STA),
STATUS = as.factor(STATUS),
AC = as.factor(AC)) ->
dataTrain
head(dataTrain, n = 10L)
- Test:
- En el conjunto de test hay 9333 vuelos registrados.
- El rango de fechas de vuelos es desde 01-05-2016 hasta 20-09-2018.
- Hay 700 vuelos registrados.
- En total son 82 puntos de destino y 84 puntos de llegada diferentes.
- Los mismos 5 estados de vuelo.
- Hay 44 códigos de aeronaves.
# Test
dataTest %>%
mutate(DATOP = as.Date(DATOP),
FLTID = as.factor(FLTID),
DEPSTN = as.factor(DEPSTN),
ARRSTN = as.factor(ARRSTN),
STD = as.POSIXct(STD),
STA = gsub("\\.", ":", STA),
STA = as.POSIXct(STA),
STATUS = as.factor(STATUS),
AC = as.factor(AC)) ->
dataTest
head(dataTest, n = 10L)
Conteos
- ¿Cuántos vuelos (FLTID) del test coinciden con el train?:
FALSE TRUE
67 9266
- ¿Cuántos puntos de destino coinciden entre el test y train?:
FALSE TRUE
3 9330
- ¿Cuántos puntos de llegada coinciden entre el test y train?:
FALSE TRUE
3 9330
- ¿Cuántos códigos de aeronave coinciden entre el test y train?:
FALSE TRUE
21 9312
Nuevas variables
- Basado en las variables originales derivo las siguientes características:
- Días del mes, días de la semana, meses y año de vuelo.
- Agrego trimestres.
- Agrego semana del año.
- Agrego una variable que indique si es fin de semana.
- Agrego una nueva variable binaria que indique si es fin de mes. Si la fecha del vuelo está entre el 28 a 31 de cada mes, lo categorizo como fin de mes.
- Agrego una nueva variable binaria con fechas especiales. Aunque son muchas las fechas especiales que podrían ser tenidas en cuenta, incluyo el día del trabajo (1 de mayo), el día de san valentín (19 de septiembre), el día internacional de la mujer (8 de marzo), año nuevo (01 de enero), día de los reyes magos (06 de enero), noche de Hallowen (31 de octubre), noche buena (24 de diciembre), día de navidad (25 de diciembre) y fin de año (31 de diciembre).
- Con el tiempo de salida obtengo la hora y establezco una nueva variable que define si el vuelo es en la madrugada, mañana, tarde o noche.
- Resto el tiempo de salida con el tiempo de llegada para conocer el tiempo promedio de vuelo. El resultado estará dado en minutos, sin embargo, lo convierto a horas dividiendo sobre 60. Hay vuelos con inconsistencias en los tiempos de partida y llegada, por tal motivo el tiempo de vuelo de aquellos que superan las 24 horas les agrego NA (363 registros en total).
- Con el tiempo promedio de vuelo clasifico los vuelos en vuelo corto (hasta 2 horas), vuelo moderado (entre 2 y 5 horas) y vuelo largo (mayor a 5 horas).
- Cuento el número de vuelos por punto de salida. Esto supongo que servirá para observar la demanda de cada sitio (ciudad o país), esperando que donde haya más demanda posiblemente haya menor capacidad de reacción (¿o al contrario?) y quizás mayores retrasos. Lo mismo hago por punto de llegada.
- Cuento número de vuelos por código de aeronave, en el mismo orden de ideas de oferta-demanda.
- Opcionales: estas variables las agrego como opcionales porque podrían causar sobreajuste en los modelos.
- Para cada vuelo -FLTID promedio el tiempo de retraso. Los vuelos que están en el train que no pertenecen al test les agrego
NA. También se podría calcular la mediana en lugar del promedio.
- Para cada vuelo obtengo la desviación estándar de la variable objetivo.
- Para cada vuelo obtengo el mínimo y máximo tiempo de retraso.
- Calculo el rango intercuartílico de retraso de cada vuelo.
Nuevas Train 1
# Nuevas variables train
dataTrain %>%
mutate(dayWeek = weekdays(DATOP),
mes = factor(month(DATOP)),
anio = factor(year(DATOP)),
trimestre = factor(quarters(DATOP)),
weekYear = week(DATOP),
endWeek = factor(if_else(dayWeek %in% c("sábado", "domingo"),
true = "Si", false = "No")),
horaVuelo = hour(STD),
horaVueloClas = if_else(
horaVuelo >= 0 & horaVuelo < 6,
true = "Magrudada",
false = if_else(
horaVuelo >= 6 & horaVuelo < 12,
true = "Mañana",
false = if_else(
horaVuelo >= 12 & horaVuelo < 18,
true = "Tarde",
false = "Noche"))),
tiempoVuelo = as.numeric(STA - STD)/60,
tiempoVuelo = ifelse(tiempoVuelo > 24, NA, tiempoVuelo),
tiempoClas = if_else(tiempoVuelo <= 2,
true = "Corto",
false = if_else(
tiempoVuelo > 5,
true = "Largo",
false = "Moderado"))) %>%
group_by(DEPSTN) %>%
mutate(vuelosPartida = n()) %>%
ungroup() %>%
group_by(ARRSTN) %>%
mutate(vuelosDestino = n()) %>%
ungroup() %>%
group_by(AC) %>%
mutate(vuelosAC = n()) %>%
group_by(FLTID) %>%
mutate(promedioRetraso = mean(target, na.rm = TRUE),
medianaRetraso = median(target, na.rm = TRUE),
desvRetraso = sd(target, na.rm = TRUE)) %>%
ungroup() %>%
mutate_if(is.character, as.factor) %>%
select(target, everything()) ->
newDataTrain
newDataTrain
Nuevas Test 1
# Nuevas variables test
dataTest %>%
mutate(dayWeek = weekdays(DATOP),
mes = factor(month(DATOP)),
anio = factor(year(DATOP)),
trimestre = factor(quarters(DATOP)),
weekYear = week(DATOP),
endWeek = factor(if_else(dayWeek %in% c("sábado", "domingo"),
true = "Si", false = "No")),
horaVuelo = hour(STD),
horaVueloClas = if_else(
horaVuelo >= 0 & horaVuelo < 6,
true = "Magrudada",
false = if_else(
horaVuelo >= 6 & horaVuelo < 12,
true = "Mañana",
false = if_else(
horaVuelo >= 12 & horaVuelo < 18,
true = "Tarde",
false = "Noche"))),
tiempoVuelo = as.numeric(STA - STD)/60,
tiempoVuelo = ifelse(tiempoVuelo > 24, NA, tiempoVuelo),
tiempoClas = if_else(tiempoVuelo <= 2,
true = "Corto",
false = if_else(
tiempoVuelo > 5,
true = "Largo",
false = "Moderado"))) %>%
group_by(DEPSTN) %>%
mutate(vuelosPartida = n()) %>%
ungroup() %>%
group_by(ARRSTN) %>%
mutate(vuelosDestino = n()) %>%
ungroup() %>%
group_by(AC) %>%
mutate(vuelosAC = n()) %>%
ungroup() %>%
mutate_if(is.character, as.factor) ->
newDataTest
# Juntando datos de tiempos de espera (target)
left_join(newDataTest,
newDataTrain %>%
select(FLTID, promedioRetraso:desvRetraso),
by = "FLTID") %>%
distinct(ID, .keep_all = TRUE) ->
newDataTest
newDataTest
Nuevas Train 2
# Fechas especiales
fechas <- c("5_1", "9_19", "3_8", "1_1", "1_6", "10_31", "12_24", "12_25",
"12_31")
# Nuevas variables train
dataTrain %>%
mutate(dayWeek = weekdays(DATOP),
mes = factor(month(DATOP)),
dia = mday(DATOP),
anio = factor(year(DATOP)),
trimestre = factor(quarters(DATOP)),
weekYear = week(DATOP),
endWeek = factor(if_else(dayWeek %in% c("sábado", "domingo"),
true = "Si", false = "No")),
finMes = if_else(dia %in% c(28, 29, 30, 31),
true = "Si", false = "No"),
horaVuelo = hour(STD),
horaVueloClas = if_else(
horaVuelo >= 0 & horaVuelo < 6,
true = "Magrudada",
false = if_else(
horaVuelo >= 6 & horaVuelo < 12,
true = "Mañana",
false = if_else(
horaVuelo >= 12 & horaVuelo < 18,
true = "Tarde",
false = "Noche"))),
tiempoVuelo = as.numeric(STA - STD)/60,
tiempoVuelo = ifelse(tiempoVuelo > 24, NA, tiempoVuelo),
tiempoClas = if_else(tiempoVuelo <= 2,
true = "Corto",
false = if_else(
tiempoVuelo > 5,
true = "Largo",
false = "Moderado"))) %>%
group_by(DEPSTN) %>%
mutate(vuelosPartida = n()) %>%
ungroup() %>%
group_by(ARRSTN) %>%
mutate(vuelosDestino = n()) %>%
ungroup() %>%
group_by(AC) %>%
mutate(vuelosAC = n()) %>%
group_by(FLTID) %>%
mutate(promedioRetraso = mean(target, na.rm = TRUE),
medianaRetraso = median(target, na.rm = TRUE),
desvRetraso = sd(target, na.rm = TRUE),
minRetraso = min(target, na.rm = TRUE),
maxRetraso = max(target, na.rm = TRUE),
RicRetraso = IQR(target, na.rm = TRUE)) %>%
ungroup() %>%
unite(mes, dia, col = "diaMes", remove = FALSE) %>%
mutate(fechaEspecial = if_else(diaMes %in% fechas,
true = "Si", false = "No")) %>%
mutate_if(is.character, as.factor) %>%
select(target, everything()) %>%
select(-diaMes) ->
newDataTrain
newDataTrain
Nuevas Test 2
# Nuevas variables test
dataTest %>%
mutate(dayWeek = weekdays(DATOP),
mes = factor(month(DATOP)),
dia = mday(DATOP),
anio = factor(year(DATOP)),
trimestre = factor(quarters(DATOP)),
weekYear = week(DATOP),
endWeek = factor(if_else(dayWeek %in% c("sábado", "domingo"),
true = "Si", false = "No")),
finMes = if_else(dia %in% c(28, 29, 30, 31),
true = "Si", false = "No"),
horaVuelo = hour(STD),
horaVueloClas = if_else(
horaVuelo >= 0 & horaVuelo < 6,
true = "Magrudada",
false = if_else(
horaVuelo >= 6 & horaVuelo < 12,
true = "Mañana",
false = if_else(
horaVuelo >= 12 & horaVuelo < 18,
true = "Tarde",
false = "Noche"))),
tiempoVuelo = as.numeric(STA - STD)/60,
tiempoVuelo = ifelse(tiempoVuelo > 24, NA, tiempoVuelo),
tiempoClas = if_else(tiempoVuelo <= 2,
true = "Corto",
false = if_else(
tiempoVuelo > 5,
true = "Largo",
false = "Moderado"))) %>%
unite(mes, dia, col = "diaMes", remove = FALSE) %>%
mutate(fechaEspecial = if_else(diaMes %in% fechas,
true = "Si", false = "No")) %>%
group_by(DEPSTN) %>%
mutate(vuelosPartida = n()) %>%
ungroup() %>%
group_by(ARRSTN) %>%
mutate(vuelosDestino = n()) %>%
ungroup() %>%
group_by(AC) %>%
mutate(vuelosAC = n()) %>%
ungroup() %>%
select(-diaMes) %>%
mutate_if(is.character, as.factor) ->
newDataTest
# Juntando datos de tiempos de espera (target)
left_join(newDataTest,
newDataTrain %>%
select(FLTID, promedioRetraso:RicRetraso),
by = "FLTID") %>%
distinct(ID, .keep_all = TRUE) ->
newDataTest
newDataTest
LS0tDQp0aXRsZTogIlJldHJhc29zIGRlIHZ1ZWxvcyINCnN1YnRpdGxlOiAiQW7DoWxpc2lzIEV4cGxvcmF0b3JpbyINCmF1dGhvcjogIlNpZGVyZXVzIg0Kb3V0cHV0Og0KICBodG1sX25vdGVib29rOg0KICAgIGNvZGVfZm9sZGluZzogaGlkZQ0KICAgIGRmX3ByaW50OiBwYWdlZA0KICAgIGNzczogZXN0aWxvLmNzcw0KICAgIHRvYzogdHJ1ZQ0KICAgIHRvY19mbG9hdDoNCiAgICAgIHNtb290aF9zY3JvbGw6IGZhbHNlDQogICAgICBjb2xsYXBzZWQ6IGZhbHNlDQogICAgaGlnaGxpZ2h0OiBicmVlemVkYXJrDQogICAgDQotLS0NCg0KYGBge3IsIGluY2x1ZGU9RkFMU0V9DQprbml0cjo6b3B0c19jaHVuayRzZXQoZWNobyA9IFRSVUUsDQogICAgICAgICAgICAgICAgICAgICAgZXZhbCA9IFRSVUUsDQogICAgICAgICAgICAgICAgICAgICAgZmlnLmFsaWduID0gImNlbnRlciIsDQogICAgICAgICAgICAgICAgICAgICAgZmlnLndpZHRoID0gOSwNCiAgICAgICAgICAgICAgICAgICAgICB3YXJuaW5nID0gRkFMU0UsDQogICAgICAgICAgICAgICAgICAgICAgbWVzc2FnZSA9IEZBTFNFKQ0KYGBgDQoNCjxjZW50ZXI+DQo8aW1nIHNyYyA9ICJpbWcvaW1nMS5wbmciIC8+DQo8L2NlbnRlcj4NCg0KIyBSZXRvDQoNCi0gW1JldG8gZW4gRGF0YVNvdXJjZS5haV0oaHR0cHM6Ly93d3cuZGF0YXNvdXJjZS5haS9lcy9ob21lL2NvbXBldGl0aW9ucy9wcmVkaWNjaW9uLWRlLXJldHJhc29zLWRlLXZ1ZWxvcy1wYXJhLXVuYS1hZXJvbGluZWEpDQoNCiMgRGF0b3MNCg0KYGBge3J9DQpsaWJyYXJ5KGRhdGEudGFibGUpDQpkYXRhVHJhaW4gPC0gZnJlYWQoIi4uL2RhdGEvdHJhaW4uY3N2IikNCmRhdGFUZXN0IDwtIGZyZWFkKCIuLi9kYXRhL3Rlc3QuY3N2IikNCmRhdGFTYW1wbGUgPC0gZnJlYWQoIi4uL2RhdGEvc2FtcGxlLmNzdiIpDQpgYGANCg0KIyBWYXJpYWJsZXMNCg0KLSAqKklEOioqIGlkZW50aWZpY2FjacOzbiBkZSB2dWVsby4NCi0gKipEQVRPUDoqKiBGZWNoYSBkZSB2dWVsby4NCi0gKipGTFRJRDoqKiBOw7ptZXJvIGRlIHZ1ZWxvDQotICoqREVQU1ROOioqIFB1bnRvIGRlIHBhcnRpZGENCi0gKipBUlJTVE46KiogUHVudG8gZGUgbGxlZ2FkYQ0KLSAqKlNURDoqKiBIb3JhIGRlIHNhbGlkYSBwcm9ncmFtYWRhDQotICoqU1RBOioqIEhvcmEgcHJldmlzdGEgZGUgbGxlZ2FkYQ0KLSAqKlNUQVRVUzoqKiBFc3RhZG8gZGVsIHZ1ZWxvDQotICoqQUM6KiogQ8OzZGlnbyBkZSBhZXJvbmF2ZQ0KLSAqKnRhcmdldDoqKiBUaWVtcG8gZGUgcmV0cmFzbw0KDQojIFRyYWluIHkgVGVzdA0KDQotICoqVHJhaW46KioNCiAgLSBFbiBlbCBjb25qdW50byBkZSBkYXRvcyBkZSAqdHJhaW4qIGhheSAxMDc4MzMgdnVlbG9zIHJlZ2lzdHJhZG9zLg0KICAtIEVsIHJhbmdvIGRlIGZlY2hhcyBkZSB2dWVsb3MgZXMgZGVzZGUgZWwgMDEtMDEtMjAxNiBoYXN0YSAzMS0xMi0yMDE4Lg0KICAtIEhheSAxODYxIHZ1ZWxvcyByZWdpc3RyYWRvcy4gQ2FkYSB2dWVsbyBwdWVkZSBlc3RhciByZWdpc3RyYWRvIGVuIGRpZmVyZW50ZXMgZmVjaGFzLg0KICAtIEVuIHRvdGFsIHNvbiAxMzIgcHVudG9zIGRlIGRlc3Rpbm8gZGlmZXJlbnRlcyB5IDEyOCBwdW50b3MgZGUgbGxlZ2FkYS4NCiAgLSBIYXkgZW4gdG90YWwgNSBlc3RhZG9zICgqU1RBVFVTKikgZGUgdnVlbG8uDQogIC0gSGF5IDY4IGPDs2RpZ29zIGRlIGFlcm9uYXZlcyBkaWZlcmVudGVzLg0KICAtIEVsIHRpZW1wbyBkZSByZXRyYXNvICgqdGFyZ2V0KikgZXN0w6EgZGFkbyBlbiBtaW51dG9zLg0KDQpgYGB7cn0NCmxpYnJhcnkodGlkeXZlcnNlKQ0KIyBUcmFpbg0KZGF0YVRyYWluICU+JSANCiAgbXV0YXRlKERBVE9QID0gYXMuRGF0ZShEQVRPUCksDQogICAgICAgICBGTFRJRCA9IGFzLmZhY3RvcihGTFRJRCksDQogICAgICAgICBERVBTVE4gPSBhcy5mYWN0b3IoREVQU1ROKSwNCiAgICAgICAgIEFSUlNUTiA9IGFzLmZhY3RvcihBUlJTVE4pLA0KICAgICAgICAgU1REID0gYXMuUE9TSVhjdChTVEQpLA0KICAgICAgICAgU1RBID0gZ3N1YigiXFwuIiwgIjoiLCBTVEEpLA0KICAgICAgICAgU1RBID0gYXMuUE9TSVhjdChTVEEpLA0KICAgICAgICAgU1RBVFVTID0gYXMuZmFjdG9yKFNUQVRVUyksDQogICAgICAgICBBQyA9IGFzLmZhY3RvcihBQykpIC0+DQogIGRhdGFUcmFpbg0KaGVhZChkYXRhVHJhaW4sIG4gPSAxMEwpDQpgYGANCg0KLSAqKlRlc3Q6KioNCiAgLSBFbiBlbCBjb25qdW50byBkZSAqdGVzdCogaGF5IDkzMzMgdnVlbG9zIHJlZ2lzdHJhZG9zLg0KICAtIEVsIHJhbmdvIGRlIGZlY2hhcyBkZSB2dWVsb3MgZXMgZGVzZGUgMDEtMDUtMjAxNiBoYXN0YSAyMC0wOS0yMDE4Lg0KICAtIEhheSA3MDAgdnVlbG9zIHJlZ2lzdHJhZG9zLg0KICAtIEVuIHRvdGFsIHNvbiA4MiBwdW50b3MgZGUgZGVzdGlubyB5IDg0IHB1bnRvcyBkZSBsbGVnYWRhIGRpZmVyZW50ZXMuDQogIC0gTG9zIG1pc21vcyA1IGVzdGFkb3MgZGUgdnVlbG8uDQogIC0gSGF5IDQ0IGPDs2RpZ29zIGRlIGFlcm9uYXZlcy4NCg0KYGBge3J9DQojIFRlc3QNCmRhdGFUZXN0ICU+JSANCiAgbXV0YXRlKERBVE9QID0gYXMuRGF0ZShEQVRPUCksDQogICAgICAgICBGTFRJRCA9IGFzLmZhY3RvcihGTFRJRCksDQogICAgICAgICBERVBTVE4gPSBhcy5mYWN0b3IoREVQU1ROKSwNCiAgICAgICAgIEFSUlNUTiA9IGFzLmZhY3RvcihBUlJTVE4pLA0KICAgICAgICAgU1REID0gYXMuUE9TSVhjdChTVEQpLA0KICAgICAgICAgU1RBID0gZ3N1YigiXFwuIiwgIjoiLCBTVEEpLA0KICAgICAgICAgU1RBID0gYXMuUE9TSVhjdChTVEEpLA0KICAgICAgICAgU1RBVFVTID0gYXMuZmFjdG9yKFNUQVRVUyksDQogICAgICAgICBBQyA9IGFzLmZhY3RvcihBQykpIC0+DQogIGRhdGFUZXN0DQpoZWFkKGRhdGFUZXN0LCBuID0gMTBMKQ0KYGBgDQoNCiMgQ29udGVvcw0KDQotICoqwr9DdcOhbnRvcyB2dWVsb3MgKCpGTFRJRCopIGRlbCB0ZXN0IGNvaW5jaWRlbiBjb24gZWwgdHJhaW4/OioqDQoNCmBgYHtyfQ0KdGFibGUoZGF0YVRlc3QkRkxUSUQgJWluJSBkYXRhVHJhaW4kRkxUSUQpDQpgYGANCg0KLSAqKsK/Q3XDoW50b3MgcHVudG9zIGRlIGRlc3Rpbm8gY29pbmNpZGVuIGVudHJlIGVsIHRlc3QgeSB0cmFpbj86KioNCg0KYGBge3J9DQp0YWJsZShkYXRhVGVzdCRERVBTVE4gJWluJSBkYXRhVHJhaW4kREVQU1ROKQ0KYGBgDQoNCi0gKirCv0N1w6FudG9zIHB1bnRvcyBkZSBsbGVnYWRhIGNvaW5jaWRlbiBlbnRyZSBlbCB0ZXN0IHkgdHJhaW4/OioqDQoNCmBgYHtyfQ0KdGFibGUoZGF0YVRlc3QkQVJSU1ROICVpbiUgZGF0YVRyYWluJEFSUlNUTikNCmBgYA0KDQotICoqwr9DdcOhbnRvcyBjw7NkaWdvcyBkZSBhZXJvbmF2ZSBjb2luY2lkZW4gZW50cmUgZWwgdGVzdCB5IHRyYWluPzoqKg0KDQpgYGB7cn0NCnRhYmxlKGRhdGFUZXN0JEFDICVpbiUgZGF0YVRyYWluJEFDKQ0KYGBgDQoNCiMgTnVldmFzIHZhcmlhYmxlcw0KDQotIEJhc2FkbyBlbiBsYXMgdmFyaWFibGVzIG9yaWdpbmFsZXMgZGVyaXZvIGxhcyBzaWd1aWVudGVzIGNhcmFjdGVyw61zdGljYXM6DQogIC0gRMOtYXMgZGVsIG1lcywgZMOtYXMgZGUgbGEgc2VtYW5hLCBtZXNlcyB5IGHDsW8gZGUgdnVlbG8uDQogIC0gQWdyZWdvIHRyaW1lc3RyZXMuDQogIC0gQWdyZWdvIHNlbWFuYSBkZWwgYcOxby4NCiAgLSBBZ3JlZ28gdW5hIHZhcmlhYmxlIHF1ZSBpbmRpcXVlIHNpIGVzIGZpbiBkZSBzZW1hbmEuDQogIC0gQWdyZWdvIHVuYSBudWV2YSB2YXJpYWJsZSBiaW5hcmlhIHF1ZSBpbmRpcXVlIHNpIGVzIGZpbiBkZSBtZXMuIFNpIGxhIGZlY2hhIGRlbCB2dWVsbyBlc3TDoSBlbnRyZSBlbCAyOCBhIDMxIGRlIGNhZGEgbWVzLCBsbyBjYXRlZ29yaXpvIGNvbW8gZmluIGRlIG1lcy4NCiAgLSBBZ3JlZ28gdW5hIG51ZXZhIHZhcmlhYmxlIGJpbmFyaWEgY29uIGZlY2hhcyBlc3BlY2lhbGVzLiBBdW5xdWUgc29uIG11Y2hhcyBsYXMgZmVjaGFzIGVzcGVjaWFsZXMgcXVlIHBvZHLDrWFuIHNlciB0ZW5pZGFzIGVuIGN1ZW50YSwgaW5jbHV5byBlbCBkw61hIGRlbCB0cmFiYWpvICgxIGRlIG1heW8pLCBlbCBkw61hIGRlIHNhbiB2YWxlbnTDrW4gKDE5IGRlIHNlcHRpZW1icmUpLCBlbCBkw61hIGludGVybmFjaW9uYWwgZGUgbGEgbXVqZXIgKDggZGUgbWFyem8pLCBhw7FvIG51ZXZvICgwMSBkZSBlbmVybyksIGTDrWEgZGUgbG9zIHJleWVzIG1hZ29zICgwNiBkZSBlbmVybyksIG5vY2hlIGRlIEhhbGxvd2VuICgzMSBkZSBvY3R1YnJlKSwgbm9jaGUgYnVlbmEgKDI0IGRlIGRpY2llbWJyZSksIGTDrWEgZGUgbmF2aWRhZCAoMjUgZGUgZGljaWVtYnJlKSB5IGZpbiBkZSBhw7FvICgzMSBkZSBkaWNpZW1icmUpLg0KICAtIENvbiBlbCB0aWVtcG8gZGUgc2FsaWRhIG9idGVuZ28gbGEgKipob3JhKiogeSBlc3RhYmxlemNvIHVuYSBudWV2YSB2YXJpYWJsZSBxdWUgZGVmaW5lIHNpIGVsIHZ1ZWxvIGVzIGVuIGxhICptYWRydWdhZGEqLCAqbWHDsWFuYSosICp0YXJkZSogbyAqbm9jaGUqLg0KICAtIFJlc3RvIGVsIHRpZW1wbyBkZSBzYWxpZGEgY29uIGVsIHRpZW1wbyBkZSBsbGVnYWRhIHBhcmEgY29ub2NlciBlbCB0aWVtcG8gcHJvbWVkaW8gZGUgdnVlbG8uIEVsIHJlc3VsdGFkbyBlc3RhcsOhIGRhZG8gZW4gbWludXRvcywgc2luIGVtYmFyZ28sIGxvIGNvbnZpZXJ0byBhIGhvcmFzIGRpdmlkaWVuZG8gc29icmUgNjAuIDx0cmVkPkhheSB2dWVsb3MgY29uIGluY29uc2lzdGVuY2lhcyBlbiBsb3MgdGllbXBvcyBkZSBwYXJ0aWRhIHkgbGxlZ2FkYSwgcG9yIHRhbCBtb3Rpdm8gZWwgdGllbXBvIGRlIHZ1ZWxvIGRlIGFxdWVsbG9zIHF1ZSBzdXBlcmFuIGxhcyAyNCBob3JhcyBsZXMgYWdyZWdvIE5BICgzNjMgcmVnaXN0cm9zIGVuIHRvdGFsKS48L3RyZWQ+DQogIC0gQ29uIGVsIHRpZW1wbyBwcm9tZWRpbyBkZSB2dWVsbyBjbGFzaWZpY28gbG9zIHZ1ZWxvcyBlbiAqdnVlbG8gY29ydG8qIChoYXN0YSAyIGhvcmFzKSwgKnZ1ZWxvIG1vZGVyYWRvKiAoZW50cmUgMiB5IDUgaG9yYXMpIHkgKnZ1ZWxvIGxhcmdvKiAobWF5b3IgYSA1IGhvcmFzKS4NCiAgLSBDdWVudG8gZWwgbsO6bWVybyBkZSB2dWVsb3MgcG9yIHB1bnRvIGRlIHNhbGlkYS4gRXN0byBzdXBvbmdvIHF1ZSBzZXJ2aXLDoSBwYXJhIG9ic2VydmFyIGxhIGRlbWFuZGEgZGUgY2FkYSBzaXRpbyAoY2l1ZGFkIG8gcGHDrXMpLCBlc3BlcmFuZG8gcXVlIGRvbmRlIGhheWEgbcOhcyBkZW1hbmRhIHBvc2libGVtZW50ZSBoYXlhIG1lbm9yIGNhcGFjaWRhZCBkZSByZWFjY2nDs24gKMK/byBhbCBjb250cmFyaW8/KSB5IHF1aXrDoXMgbWF5b3JlcyByZXRyYXNvcy4gTG8gbWlzbW8gaGFnbyBwb3IgcHVudG8gZGUgbGxlZ2FkYS4NCiAgLSBDdWVudG8gbsO6bWVybyBkZSB2dWVsb3MgcG9yIGPDs2RpZ28gZGUgYWVyb25hdmUsIGVuIGVsIG1pc21vIG9yZGVuIGRlIGlkZWFzIGRlICpvZmVydGEtZGVtYW5kYSouDQogIC0gKipPcGNpb25hbGVzOioqIGVzdGFzIHZhcmlhYmxlcyBsYXMgYWdyZWdvIGNvbW8gb3BjaW9uYWxlcyBwb3JxdWUgcG9kcsOtYW4gY2F1c2FyIHNvYnJlYWp1c3RlIGVuIGxvcyBtb2RlbG9zLg0KICAgIC0gUGFyYSBjYWRhIHZ1ZWxvICotRkxUSUQqIHByb21lZGlvIGVsIHRpZW1wbyBkZSByZXRyYXNvLiBMb3MgdnVlbG9zIHF1ZSBlc3TDoW4gZW4gZWwgKnRyYWluKiBxdWUgbm8gcGVydGVuZWNlbiBhbCAqdGVzdCogbGVzIGFncmVnbyBgTkFgLiBUYW1iacOpbiBzZSBwb2Ryw61hIGNhbGN1bGFyIGxhIG1lZGlhbmEgZW4gbHVnYXIgZGVsIHByb21lZGlvLg0KICAgIC0gUGFyYSBjYWRhIHZ1ZWxvIG9idGVuZ28gbGEgZGVzdmlhY2nDs24gZXN0w6FuZGFyIGRlIGxhIHZhcmlhYmxlIG9iamV0aXZvLg0KICAgIC0gUGFyYSBjYWRhIHZ1ZWxvIG9idGVuZ28gZWwgbcOtbmltbyB5IG3DoXhpbW8gdGllbXBvIGRlIHJldHJhc28uDQogICAgLSBDYWxjdWxvIGVsIHJhbmdvIGludGVyY3VhcnTDrWxpY28gZGUgcmV0cmFzbyBkZSBjYWRhIHZ1ZWxvLg0KDQojIyBOdWV2YXMgVHJhaW4gMQ0KDQpgYGB7cn0NCiMgTnVldmFzIHZhcmlhYmxlcyB0cmFpbg0KZGF0YVRyYWluICU+JSANCiAgbXV0YXRlKGRheVdlZWsgPSB3ZWVrZGF5cyhEQVRPUCksDQogICAgICAgICBtZXMgPSBmYWN0b3IobW9udGgoREFUT1ApKSwNCiAgICAgICAgIGFuaW8gPSBmYWN0b3IoeWVhcihEQVRPUCkpLA0KICAgICAgICAgdHJpbWVzdHJlID0gZmFjdG9yKHF1YXJ0ZXJzKERBVE9QKSksDQogICAgICAgICB3ZWVrWWVhciA9IHdlZWsoREFUT1ApLA0KICAgICAgICAgZW5kV2VlayA9IGZhY3RvcihpZl9lbHNlKGRheVdlZWsgJWluJSBjKCJzw6FiYWRvIiwgImRvbWluZ28iKSwNCiAgICAgICAgICAgICAgICAgICAgICAgICAgIHRydWUgPSAiU2kiLCBmYWxzZSA9ICJObyIpKSwNCiAgICAgICAgIGhvcmFWdWVsbyA9IGhvdXIoU1REKSwNCiAgICAgICAgIGhvcmFWdWVsb0NsYXMgPSBpZl9lbHNlKA0KICAgICAgICAgICBob3JhVnVlbG8gPj0gMCAmIGhvcmFWdWVsbyA8IDYsDQogICAgICAgICAgIHRydWUgPSAiTWFncnVkYWRhIiwNCiAgICAgICAgICAgZmFsc2UgPSBpZl9lbHNlKA0KICAgICAgICAgICAgIGhvcmFWdWVsbyA+PSA2ICYgaG9yYVZ1ZWxvIDwgMTIsDQogICAgICAgICAgICAgdHJ1ZSA9ICJNYcOxYW5hIiwNCiAgICAgICAgICAgICBmYWxzZSA9IGlmX2Vsc2UoDQogICAgICAgICAgICAgICBob3JhVnVlbG8gPj0gMTIgJiBob3JhVnVlbG8gPCAxOCwNCiAgICAgICAgICAgICAgIHRydWUgPSAiVGFyZGUiLA0KICAgICAgICAgICAgICAgZmFsc2UgPSAiTm9jaGUiKSkpLA0KICAgICAgICAgdGllbXBvVnVlbG8gPSBhcy5udW1lcmljKFNUQSAtIFNURCkvNjAsDQogICAgICAgICB0aWVtcG9WdWVsbyA9IGlmZWxzZSh0aWVtcG9WdWVsbyA+IDI0LCBOQSwgdGllbXBvVnVlbG8pLA0KICAgICAgICAgdGllbXBvQ2xhcyA9IGlmX2Vsc2UodGllbXBvVnVlbG8gPD0gMiwNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHRydWUgPSAiQ29ydG8iLA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgZmFsc2UgPSBpZl9lbHNlKA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICB0aWVtcG9WdWVsbyA+IDUsDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHRydWUgPSAiTGFyZ28iLA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBmYWxzZSA9ICJNb2RlcmFkbyIpKSkgJT4lIA0KICBncm91cF9ieShERVBTVE4pICU+JSANCiAgbXV0YXRlKHZ1ZWxvc1BhcnRpZGEgPSBuKCkpICU+JSANCiAgdW5ncm91cCgpICU+JSANCiAgZ3JvdXBfYnkoQVJSU1ROKSAlPiUgDQogIG11dGF0ZSh2dWVsb3NEZXN0aW5vID0gbigpKSAlPiUgDQogIHVuZ3JvdXAoKSAlPiUgDQogIGdyb3VwX2J5KEFDKSAlPiUgDQogIG11dGF0ZSh2dWVsb3NBQyA9IG4oKSkgJT4lIA0KICBncm91cF9ieShGTFRJRCkgJT4lIA0KICBtdXRhdGUocHJvbWVkaW9SZXRyYXNvID0gbWVhbih0YXJnZXQsIG5hLnJtID0gVFJVRSksDQogICAgICAgICBtZWRpYW5hUmV0cmFzbyA9IG1lZGlhbih0YXJnZXQsIG5hLnJtID0gVFJVRSksDQogICAgICAgICBkZXN2UmV0cmFzbyA9IHNkKHRhcmdldCwgbmEucm0gPSBUUlVFKSkgJT4lIA0KICB1bmdyb3VwKCkgJT4lIA0KICBtdXRhdGVfaWYoaXMuY2hhcmFjdGVyLCBhcy5mYWN0b3IpICU+JSANCiAgc2VsZWN0KHRhcmdldCwgZXZlcnl0aGluZygpKSAtPg0KICBuZXdEYXRhVHJhaW4NCm5ld0RhdGFUcmFpbg0KYGBgDQoNCiMjIE51ZXZhcyBUZXN0IDENCg0KYGBge3J9DQojIE51ZXZhcyB2YXJpYWJsZXMgdGVzdA0KZGF0YVRlc3QgJT4lIA0KICBtdXRhdGUoZGF5V2VlayA9IHdlZWtkYXlzKERBVE9QKSwNCiAgICAgICAgIG1lcyA9IGZhY3Rvcihtb250aChEQVRPUCkpLA0KICAgICAgICAgYW5pbyA9IGZhY3Rvcih5ZWFyKERBVE9QKSksDQogICAgICAgICB0cmltZXN0cmUgPSBmYWN0b3IocXVhcnRlcnMoREFUT1ApKSwNCiAgICAgICAgIHdlZWtZZWFyID0gd2VlayhEQVRPUCksDQogICAgICAgICBlbmRXZWVrID0gZmFjdG9yKGlmX2Vsc2UoZGF5V2VlayAlaW4lIGMoInPDoWJhZG8iLCAiZG9taW5nbyIpLA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgdHJ1ZSA9ICJTaSIsIGZhbHNlID0gIk5vIikpLA0KICAgICAgICAgaG9yYVZ1ZWxvID0gaG91cihTVEQpLA0KICAgICAgICAgaG9yYVZ1ZWxvQ2xhcyA9IGlmX2Vsc2UoDQogICAgICAgICAgIGhvcmFWdWVsbyA+PSAwICYgaG9yYVZ1ZWxvIDwgNiwNCiAgICAgICAgICAgdHJ1ZSA9ICJNYWdydWRhZGEiLA0KICAgICAgICAgICBmYWxzZSA9IGlmX2Vsc2UoDQogICAgICAgICAgICAgaG9yYVZ1ZWxvID49IDYgJiBob3JhVnVlbG8gPCAxMiwNCiAgICAgICAgICAgICB0cnVlID0gIk1hw7FhbmEiLA0KICAgICAgICAgICAgIGZhbHNlID0gaWZfZWxzZSgNCiAgICAgICAgICAgICAgIGhvcmFWdWVsbyA+PSAxMiAmIGhvcmFWdWVsbyA8IDE4LA0KICAgICAgICAgICAgICAgdHJ1ZSA9ICJUYXJkZSIsDQogICAgICAgICAgICAgICBmYWxzZSA9ICJOb2NoZSIpKSksDQogICAgICAgICB0aWVtcG9WdWVsbyA9IGFzLm51bWVyaWMoU1RBIC0gU1REKS82MCwNCiAgICAgICAgIHRpZW1wb1Z1ZWxvID0gaWZlbHNlKHRpZW1wb1Z1ZWxvID4gMjQsIE5BLCB0aWVtcG9WdWVsbyksDQogICAgICAgICB0aWVtcG9DbGFzID0gaWZfZWxzZSh0aWVtcG9WdWVsbyA8PSAyLA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgdHJ1ZSA9ICJDb3J0byIsDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICBmYWxzZSA9IGlmX2Vsc2UoDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHRpZW1wb1Z1ZWxvID4gNSwNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgdHJ1ZSA9ICJMYXJnbyIsDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGZhbHNlID0gIk1vZGVyYWRvIikpKSAlPiUgDQogIGdyb3VwX2J5KERFUFNUTikgJT4lIA0KICBtdXRhdGUodnVlbG9zUGFydGlkYSA9IG4oKSkgJT4lIA0KICB1bmdyb3VwKCkgJT4lIA0KICBncm91cF9ieShBUlJTVE4pICU+JSANCiAgbXV0YXRlKHZ1ZWxvc0Rlc3Rpbm8gPSBuKCkpICU+JSANCiAgdW5ncm91cCgpICU+JSANCiAgZ3JvdXBfYnkoQUMpICU+JSANCiAgbXV0YXRlKHZ1ZWxvc0FDID0gbigpKSAlPiUgDQogIHVuZ3JvdXAoKSAlPiUgDQogIG11dGF0ZV9pZihpcy5jaGFyYWN0ZXIsIGFzLmZhY3RvcikgLT4NCiAgbmV3RGF0YVRlc3QNCiMgSnVudGFuZG8gZGF0b3MgZGUgdGllbXBvcyBkZSBlc3BlcmEgKHRhcmdldCkNCmxlZnRfam9pbihuZXdEYXRhVGVzdCwNCiAgICAgICAgICBuZXdEYXRhVHJhaW4gJT4lDQogICAgICAgICAgICBzZWxlY3QoRkxUSUQsIHByb21lZGlvUmV0cmFzbzpkZXN2UmV0cmFzbyksDQogICAgICAgICAgYnkgPSAiRkxUSUQiKSAlPiUNCiAgZGlzdGluY3QoSUQsIC5rZWVwX2FsbCA9IFRSVUUpIC0+DQogIG5ld0RhdGFUZXN0DQpuZXdEYXRhVGVzdA0KYGBgDQoNCiMjIEV4cG9ydGFuZG8gZGF0b3MgMQ0KDQpgYGB7cn0NCiMgTnVldmFzIDENCnNhdmUobmV3RGF0YVRyYWluLCBmaWxlID0gIi4uL215RGF0YS9UcmFpbjEuUmRhdGEiLCBjb21wcmVzcyA9ICJ4eiIpDQpzYXZlKG5ld0RhdGFUZXN0LCBmaWxlID0gIi4uL215RGF0YS9UZXN0MS5SZGF0YSIsIGNvbXByZXNzID0gInh6IikNCmBgYA0KDQojIyBOdWV2YXMgVHJhaW4gMg0KDQpgYGB7cn0NCiMgRmVjaGFzIGVzcGVjaWFsZXMNCmZlY2hhcyA8LSBjKCI1XzEiLCAiOV8xOSIsICIzXzgiLCAiMV8xIiwgIjFfNiIsICIxMF8zMSIsICIxMl8yNCIsICIxMl8yNSIsDQogICAgICAgICAgICAiMTJfMzEiKQ0KDQojIE51ZXZhcyB2YXJpYWJsZXMgdHJhaW4NCmRhdGFUcmFpbiAlPiUgDQogIG11dGF0ZShkYXlXZWVrID0gd2Vla2RheXMoREFUT1ApLA0KICAgICAgICAgbWVzID0gZmFjdG9yKG1vbnRoKERBVE9QKSksDQogICAgICAgICBkaWEgPSBtZGF5KERBVE9QKSwNCiAgICAgICAgIGFuaW8gPSBmYWN0b3IoeWVhcihEQVRPUCkpLA0KICAgICAgICAgdHJpbWVzdHJlID0gZmFjdG9yKHF1YXJ0ZXJzKERBVE9QKSksDQogICAgICAgICB3ZWVrWWVhciA9IHdlZWsoREFUT1ApLA0KICAgICAgICAgZW5kV2VlayA9IGZhY3RvcihpZl9lbHNlKGRheVdlZWsgJWluJSBjKCJzw6FiYWRvIiwgImRvbWluZ28iKSwNCiAgICAgICAgICAgICAgICAgICAgICAgICAgIHRydWUgPSAiU2kiLCBmYWxzZSA9ICJObyIpKSwNCiAgICAgICAgIGZpbk1lcyA9IGlmX2Vsc2UoZGlhICVpbiUgYygyOCwgMjksIDMwLCAzMSksDQogICAgICAgICAgICAgICAgICAgICAgICAgIHRydWUgPSAiU2kiLCBmYWxzZSA9ICJObyIpLA0KICAgICAgICAgaG9yYVZ1ZWxvID0gaG91cihTVEQpLA0KICAgICAgICAgaG9yYVZ1ZWxvQ2xhcyA9IGlmX2Vsc2UoDQogICAgICAgICAgIGhvcmFWdWVsbyA+PSAwICYgaG9yYVZ1ZWxvIDwgNiwNCiAgICAgICAgICAgdHJ1ZSA9ICJNYWdydWRhZGEiLA0KICAgICAgICAgICBmYWxzZSA9IGlmX2Vsc2UoDQogICAgICAgICAgICAgaG9yYVZ1ZWxvID49IDYgJiBob3JhVnVlbG8gPCAxMiwNCiAgICAgICAgICAgICB0cnVlID0gIk1hw7FhbmEiLA0KICAgICAgICAgICAgIGZhbHNlID0gaWZfZWxzZSgNCiAgICAgICAgICAgICAgIGhvcmFWdWVsbyA+PSAxMiAmIGhvcmFWdWVsbyA8IDE4LA0KICAgICAgICAgICAgICAgdHJ1ZSA9ICJUYXJkZSIsDQogICAgICAgICAgICAgICBmYWxzZSA9ICJOb2NoZSIpKSksDQogICAgICAgICB0aWVtcG9WdWVsbyA9IGFzLm51bWVyaWMoU1RBIC0gU1REKS82MCwNCiAgICAgICAgIHRpZW1wb1Z1ZWxvID0gaWZlbHNlKHRpZW1wb1Z1ZWxvID4gMjQsIE5BLCB0aWVtcG9WdWVsbyksDQogICAgICAgICB0aWVtcG9DbGFzID0gaWZfZWxzZSh0aWVtcG9WdWVsbyA8PSAyLA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgdHJ1ZSA9ICJDb3J0byIsDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICBmYWxzZSA9IGlmX2Vsc2UoDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHRpZW1wb1Z1ZWxvID4gNSwNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgdHJ1ZSA9ICJMYXJnbyIsDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGZhbHNlID0gIk1vZGVyYWRvIikpKSAlPiUgDQogIGdyb3VwX2J5KERFUFNUTikgJT4lIA0KICBtdXRhdGUodnVlbG9zUGFydGlkYSA9IG4oKSkgJT4lIA0KICB1bmdyb3VwKCkgJT4lIA0KICBncm91cF9ieShBUlJTVE4pICU+JSANCiAgbXV0YXRlKHZ1ZWxvc0Rlc3Rpbm8gPSBuKCkpICU+JSANCiAgdW5ncm91cCgpICU+JSANCiAgZ3JvdXBfYnkoQUMpICU+JSANCiAgbXV0YXRlKHZ1ZWxvc0FDID0gbigpKSAlPiUgDQogIGdyb3VwX2J5KEZMVElEKSAlPiUgDQogIG11dGF0ZShwcm9tZWRpb1JldHJhc28gPSBtZWFuKHRhcmdldCwgbmEucm0gPSBUUlVFKSwNCiAgICAgICAgIG1lZGlhbmFSZXRyYXNvID0gbWVkaWFuKHRhcmdldCwgbmEucm0gPSBUUlVFKSwNCiAgICAgICAgIGRlc3ZSZXRyYXNvID0gc2QodGFyZ2V0LCBuYS5ybSA9IFRSVUUpLA0KICAgICAgICAgbWluUmV0cmFzbyA9IG1pbih0YXJnZXQsIG5hLnJtID0gVFJVRSksDQogICAgICAgICBtYXhSZXRyYXNvID0gbWF4KHRhcmdldCwgbmEucm0gPSBUUlVFKSwNCiAgICAgICAgIFJpY1JldHJhc28gPSBJUVIodGFyZ2V0LCBuYS5ybSA9IFRSVUUpKSAlPiUgDQogIHVuZ3JvdXAoKSAlPiUgDQogIHVuaXRlKG1lcywgZGlhLCBjb2wgPSAiZGlhTWVzIiwgcmVtb3ZlID0gRkFMU0UpICU+JSANCiAgbXV0YXRlKGZlY2hhRXNwZWNpYWwgPSBpZl9lbHNlKGRpYU1lcyAlaW4lIGZlY2hhcywNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHRydWUgPSAiU2kiLCBmYWxzZSA9ICJObyIpKSAlPiUgDQogIG11dGF0ZV9pZihpcy5jaGFyYWN0ZXIsIGFzLmZhY3RvcikgJT4lIA0KICBzZWxlY3QodGFyZ2V0LCBldmVyeXRoaW5nKCkpICU+JSANCiAgc2VsZWN0KC1kaWFNZXMpIC0+DQogIG5ld0RhdGFUcmFpbg0KICANCm5ld0RhdGFUcmFpbg0KYGBgDQoNCg0KIyMgTnVldmFzIFRlc3QgMg0KDQpgYGB7cn0NCiMgTnVldmFzIHZhcmlhYmxlcyB0ZXN0DQpkYXRhVGVzdCAlPiUgDQogIG11dGF0ZShkYXlXZWVrID0gd2Vla2RheXMoREFUT1ApLA0KICAgICAgICAgbWVzID0gZmFjdG9yKG1vbnRoKERBVE9QKSksDQogICAgICAgICBkaWEgPSBtZGF5KERBVE9QKSwNCiAgICAgICAgIGFuaW8gPSBmYWN0b3IoeWVhcihEQVRPUCkpLA0KICAgICAgICAgdHJpbWVzdHJlID0gZmFjdG9yKHF1YXJ0ZXJzKERBVE9QKSksDQogICAgICAgICB3ZWVrWWVhciA9IHdlZWsoREFUT1ApLA0KICAgICAgICAgZW5kV2VlayA9IGZhY3RvcihpZl9lbHNlKGRheVdlZWsgJWluJSBjKCJzw6FiYWRvIiwgImRvbWluZ28iKSwNCiAgICAgICAgICAgICAgICAgICAgICAgICAgIHRydWUgPSAiU2kiLCBmYWxzZSA9ICJObyIpKSwNCiAgICAgICAgIGZpbk1lcyA9IGlmX2Vsc2UoZGlhICVpbiUgYygyOCwgMjksIDMwLCAzMSksDQogICAgICAgICAgICAgICAgICAgICAgICAgIHRydWUgPSAiU2kiLCBmYWxzZSA9ICJObyIpLA0KICAgICAgICAgaG9yYVZ1ZWxvID0gaG91cihTVEQpLA0KICAgICAgICAgaG9yYVZ1ZWxvQ2xhcyA9IGlmX2Vsc2UoDQogICAgICAgICAgIGhvcmFWdWVsbyA+PSAwICYgaG9yYVZ1ZWxvIDwgNiwNCiAgICAgICAgICAgdHJ1ZSA9ICJNYWdydWRhZGEiLA0KICAgICAgICAgICBmYWxzZSA9IGlmX2Vsc2UoDQogICAgICAgICAgICAgaG9yYVZ1ZWxvID49IDYgJiBob3JhVnVlbG8gPCAxMiwNCiAgICAgICAgICAgICB0cnVlID0gIk1hw7FhbmEiLA0KICAgICAgICAgICAgIGZhbHNlID0gaWZfZWxzZSgNCiAgICAgICAgICAgICAgIGhvcmFWdWVsbyA+PSAxMiAmIGhvcmFWdWVsbyA8IDE4LA0KICAgICAgICAgICAgICAgdHJ1ZSA9ICJUYXJkZSIsDQogICAgICAgICAgICAgICBmYWxzZSA9ICJOb2NoZSIpKSksDQogICAgICAgICB0aWVtcG9WdWVsbyA9IGFzLm51bWVyaWMoU1RBIC0gU1REKS82MCwNCiAgICAgICAgIHRpZW1wb1Z1ZWxvID0gaWZlbHNlKHRpZW1wb1Z1ZWxvID4gMjQsIE5BLCB0aWVtcG9WdWVsbyksDQogICAgICAgICB0aWVtcG9DbGFzID0gaWZfZWxzZSh0aWVtcG9WdWVsbyA8PSAyLA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgdHJ1ZSA9ICJDb3J0byIsDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICBmYWxzZSA9IGlmX2Vsc2UoDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHRpZW1wb1Z1ZWxvID4gNSwNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgdHJ1ZSA9ICJMYXJnbyIsDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGZhbHNlID0gIk1vZGVyYWRvIikpKSAlPiUgDQogIHVuaXRlKG1lcywgZGlhLCBjb2wgPSAiZGlhTWVzIiwgcmVtb3ZlID0gRkFMU0UpICU+JSANCiAgbXV0YXRlKGZlY2hhRXNwZWNpYWwgPSBpZl9lbHNlKGRpYU1lcyAlaW4lIGZlY2hhcywNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHRydWUgPSAiU2kiLCBmYWxzZSA9ICJObyIpKSAlPiUgDQogIGdyb3VwX2J5KERFUFNUTikgJT4lIA0KICBtdXRhdGUodnVlbG9zUGFydGlkYSA9IG4oKSkgJT4lIA0KICB1bmdyb3VwKCkgJT4lIA0KICBncm91cF9ieShBUlJTVE4pICU+JSANCiAgbXV0YXRlKHZ1ZWxvc0Rlc3Rpbm8gPSBuKCkpICU+JSANCiAgdW5ncm91cCgpICU+JSANCiAgZ3JvdXBfYnkoQUMpICU+JSANCiAgbXV0YXRlKHZ1ZWxvc0FDID0gbigpKSAlPiUgDQogIHVuZ3JvdXAoKSAlPiUgDQogIHNlbGVjdCgtZGlhTWVzKSAlPiUgDQogIG11dGF0ZV9pZihpcy5jaGFyYWN0ZXIsIGFzLmZhY3RvcikgLT4NCiAgbmV3RGF0YVRlc3QNCg0KIyBKdW50YW5kbyBkYXRvcyBkZSB0aWVtcG9zIGRlIGVzcGVyYSAodGFyZ2V0KQ0KbGVmdF9qb2luKG5ld0RhdGFUZXN0LA0KICAgICAgICAgIG5ld0RhdGFUcmFpbiAlPiUNCiAgICAgICAgICAgIHNlbGVjdChGTFRJRCwgcHJvbWVkaW9SZXRyYXNvOlJpY1JldHJhc28pLA0KICAgICAgICAgIGJ5ID0gIkZMVElEIikgJT4lDQogIGRpc3RpbmN0KElELCAua2VlcF9hbGwgPSBUUlVFKSAtPg0KICBuZXdEYXRhVGVzdA0KDQpuZXdEYXRhVGVzdA0KYGBgDQoNCiMjIEV4cG9ydGFuZG8gZGF0b3MgMg0KDQpgYGB7cn0NCiMgTnVldmFzIDINCnNhdmUobmV3RGF0YVRyYWluLCBmaWxlID0gIi4uL215RGF0YS9UcmFpbjIuUmRhdGEiLCBjb21wcmVzcyA9ICJ4eiIpDQpzYXZlKG5ld0RhdGFUZXN0LCBmaWxlID0gIi4uL215RGF0YS9UZXN0Mi5SZGF0YSIsIGNvbXByZXNzID0gInh6IikNCmBgYA0KDQo=